<?php
/**
 * 
 */

/**
 * Class containing all constants utilized by the Nephtali Framework.
 * @author Adam Richardson
 *
 */
class NC
{
    /**
     * Display errors in the output.
     */
    const CONF_ERRORS_DISPLAY = 9;
    /**
     * Suppress error output to the screen and log the error results.
     */
    const CONF_ERRORS_SUPPRESS_AND_LOG = 10;
    /**
     * Suppress error output to the screen.
     */
    const CONF_ERRORS_SUPPRESS = 11;
    /**
     * Debug capabilities turned off.
     */
    const CONF_DEBUG_OFF = 14;
    /**
     * Debug data appended to the display of the current page.
     */
    const CONF_DEBUG_APPEND_INFO = 15;
    /**
     * Any post requests that don't include file uploads are automatically redirected to avoid back-button issues.
     */
    const CONF_POST_AUTO_REDIRECT_WO_FILES = 16;
    /**
     * All post requests are automatically redirected to avoid back-button issues.
     */
    const CONF_POST_AUTO_REDIRECT_ALL = 17;
    /**
     * Automatic post redirects are turned off.
     */
    const CONF_POST_AUTO_REDIRECT_OFF = 18;
    /**
     * Allow integers.
     */
    const FILTER_INT = 1;
    /**
     * Allow positive or negative numbers with decimal points.
     */
    const FILTER_DOUBLE = 2;
    /**
     * Allow alphanumeric text.
     */
    const FILTER_ALPHANUM = 3;
    /**
     * Allow characters typically used in variable names.
     */
    const FILTER_VAR_NAME = 19;
    /**
     * Allow valid email addresses.
     */
    const FILTER_EMAIL = 4;
    /**
     * Allow valid URL's.
     */
    const FILTER_URL = 5;
    /**
     * Allow valid US zip codes.
     */
    const FILTER_US_ZIP = 10;
    /**
     * Allow valid US phone numbers.
     */
    const FILTER_US_PHONE = 11;
    /**
     * Allow a single line of text except for XML markup and danerous hackemes.
     */
    const FILTER_TEXT = 7;
    /**
     * Allow multiple lines of text except for XML markup and dangerous hackemes.
     */
    const FILTER_TEXT_MULTILINE = 9;
    /**
     * Allow all types of text except for dangerous hackemes.
     */
    const FILTER_XML = 6;
    /**
     * Allow everything under the sun.
     */
    const FILTER_ALL = 8;
    /**
     * Allow data that validates with custom regex (you must supply the regex in the settings array for the port.)
     */
    const FILTER_CUSTOM = 21;
    /**
     * Regex used for validating positive integers.
     */
    const REGEX_INT = '/^\d+$/';
    /**
     * Regex used for validating double numbers.
     */
    const REGEX_DOUBLE = '/^-?\d+(\.\d+)?$/';
    /**
     * Regex used for validating alphanumeric data (a - z, 0 - 9, case insensitive.)
     */
    const REGEX_ALPHANUM = '/^[a-zA-Z0-9]+$/';
    /**
     * Regex used for validating variable names (a - z, 0 - 9, '_', '-', case insensitive.)
     */
    const REGEX_VAR_NAME = '/^[-a-zA-Z0-9_]+$/';
    /**
     * Regex used for validating US phone numbers.
     */
    const REGEX_US_PHONE = '/^((1)?[2-9]\d{9})|(((1)?\([2-9]\d{2}\))|((1-)?[2-9]\d{2}-))\d{3}-\d{4}$/';
    /**
     * Regex used for validating US Zip codes.
     */
    const REGEX_US_ZIP = '/^\d{5}((-)?\d{4})?$/';
    /**
     * Regex used for validating basic text, free of HTML special characters.
     */
    const REGEX_TEXT = '/^[ \/a-zA-Z0-9.!@#$%&*:;?()_+=\'",\[\]-]+$/';
    /**
     * Regex used for validating basic multiline text, free of HTML special characters.
     */
    const REGEX_TEXT_MULTILINE = '/^[ \/a-zA-Z0-9.!@#$%&*:;?()_+=\'"\n\r,\[\]-]+$/';
    /**
     * Regex used for validating text that contains XML.
     */
    const REGEX_XML = '/^(.|\n)+$/';
    /**
     * Regex used for file names, which allows letters, numbers, whitespace, '.', and the underscore.
     */
    const REGEX_FILE_NAME = '/^[-a-zA-Z0-9_\s\.]+$/';
    /**
     * Regex used for URL's.
     */
    const REGEX_URL = '/^(http|https):\/\/(([-a-zA-Z0-9]+)\.)*([-a-zA-Z0-9]+)\.([a-z]{2,6})(\/([-a-zA-Z0-9#_.=?+&;:%])+)*$/';
    /**
     * Regex used for email addresses.
     */
    const REGEX_EMAIL = '/^[a-z0-9!#$%&*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&*+=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+([a-z]{2,6})$/';
    /**
     * Request for standard page output.
     */
    const REQUEST_STANDARD = 19;
    /**
     * Request for the output of one particular pipe.
     */
    const REQUEST_HTMLFRAG = 20;
    /**
     * Whitelist regex that Pith uses to allow links through automatic encoding
     */
    const WHITELIST_A_TAG = '/<a href="(http|https):\/\/(([-a-zA-Z0-9]+)\.)*([-a-zA-Z0-9]+)\.([a-z]{2,6})(\/([-a-zA-Z0-9#_.=?+&;:%])+)*">[ a-zA-Z0-9,)(\.\?!-]+<\/a>/';
}
/**
 * Class containing all functions utilized by the Nephtali Framework.
 * @author Adam Richardson
 *
 */
class N
{
    /**
     * Output the value for debugging purposes.  Shorthand for calling exit(var_dump($value)).
     * @param mixed $debug_value
     * @return void
     */
    public static function _($debug_value)
    {
        exit(var_dump($debug_value));
    }
    /**
     * Provides access to config values.
     * @param string $key
     * @return mixed
     */
    public static function config($key)
    {
        static $values = 'empty';
        if (is_array($key) && $values == 'empty')
        {
            $values = $key;
            return;
        }
        else
        {
            if (!isset($values[$key]))
                throw new Exception('There is no config key that matches the value \''.$key.'\'.');
                
            return $values[$key];
        }
    }
    /**
     * Retrieves the contents of a url via CURL
     * @param string $url
     * @param int $connect_timeout
     * @param int $transaction_timeout
     * @return string
     */
    public static function curl_get_contents($url, $connect_timeout = 1, $transaction_timeout = 2)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
        curl_setopt($ch, CURLOPT_TIMEOUT, $transaction_timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $str = curl_exec($ch);
        $error_no = curl_errno($ch);
        curl_close ($ch);
        
        if($error_no != 0)
        {
            if(is_int($error_no))
            {
                throw new Exception('There was an error retreiving the string contents of the url \''.$url.'\'. CURL error number:'.$error_no);
            }
            else
            {
                throw new Exception('There was an error saving the url \''.$url.'\', and the CURL error number was invalid, too.');
            }
        }
        else
        {
            return $str;
        }
    }
    /**
     * Retrieves the entries (title, link, description, and updated fields) for an atom feed.
     * @param string $url
     * @param int $limit
     * @return array
     */
    public static function feed_atom($url, $limit = null)
    {
        $doc = new DOMDocument();
        $doc->loadXML(self::curl_get_contents($url));
        $rows = array();

        foreach ($doc->getElementsByTagName('entry') as $node)
        {
            if($limit > count($rows))
            {
                $rows[] = array (
                    'title'     => trim($node->getElementsByTagName('title')->item(0)->nodeValue),
                    'content'   => trim($node->getElementsByTagName('content')->item(0)->nodeValue),
                    'link'      => trim($node->getElementsByTagName('link')->item(0)->getAttribute('href')),
                    'updated'   => trim($node->getElementsByTagName('updated')->item(0)->nodeValue)
                );
            }
            else
            {
                break;
            }
        }

        return $rows;
    }
    /**
     * Retrieve the search results (title, url, description) for a Bing-based site query through the API.
     * @param string $site
     * @param string $app_id
     * @param string $query
     * @param int $limit
     * @return array
     */
    public static function feed_bing_search_site($site, $app_id, $query, $limit = 20)
    {
        $query = str_replace(' ', '+', $query);

        $url = "http://api.search.live.net/json.aspx?Appid=".urlencode($app_id)."&query=site:".urlencode($site)."+".urlencode($query)."&sources=web&web.count=".urlencode($limit);

        $json_results = json_decode(self::curl_get_contents($url), true);

        $results = $json_results['SearchResponse']['Web']['Results'];

        for ($i = 0; $i < count($results); $i++)
        {
            $results[$i] = array_change_key_case($results[$i]);
        }

        return $results;
    }
    /**
     * Retrieve the search results (title, url, clickurl, abstract, date, size) for a Yahoo! Boss-based site query through the API.
     * @param string $site
     * @param string $app_id
     * @param string $query
     * @param int $limit
     * @return array
     */
    public static function feed_yahoo_search_site($site, $app_id, $query, $limit = 20)
    {
        $url = 'http://boss.yahooapis.com/ysearch/web/v1/'.urlencode($query).'?appid='.urlencode($app_id).'&sites='.urlencode($site).'&count='.urlencode($limit);

        $json_results = json_decode(self::curl_get_contents($url), true);

        return $json_results['ysearchresponse']['resultset_web'];
    }
    /**
     * Retrieves the items (title, link, description, and updated fields) for an RSS feed.
     * @param string $url
     * @param int $limit
     * @return array
     */
    public static function feed_rss($url, $limit = null)
    {
        $doc = new DOMDocument();
        $doc->loadXML(self::curl_get_contents($url));
        $rows = array();

        foreach ($doc->getElementsByTagName('item') as $node)
        {
            if($limit > count($rows))
            {
                $rows[] = array (
                    'title' => trim($node->getElementsByTagName('title')->item(0)->nodeValue),
                    'description'  => trim($node->getElementsByTagName('description')->item(0)->nodeValue),
                    'link'  => trim($node->getElementsByTagName('link')->item(0)->nodeValue),
                    'pubDate'  => trim($node->getElementsByTagName('pubDate')->item(0)->nodeValue)
                );
            }
            else
            {
                break;
            }
        }

        return $rows;
    }
    /**
     * Register a pipe for the page.  If the $paths argument is passed in, pipes will only be registered if the path matches that of the current page.
     * @param string $name
     * @param string $function
     * @param array $required_pipes
     * @param array $paths
     * @return void
     */
    public static function pipe_register($name, $function, array $required_pipes=array(), $paths = array())
    {
        if (!$paths) return N::z_pipe_values('set', array('name' => $name, 'function' => $function, 'required_pipes' => $required_pipes));

        foreach ($paths as $path)
        {
            if (strpos($_SERVER['PHP_SELF'], $path) === 0)
            {
                return N::z_pipe_values('set', array('name' => $name, 'function' => $function, 'required_pipes' => $required_pipes));
            }
        }
    }
    /**
     * Retrieve the pipe settings by name.
     * @param string name
     * @return array
     */
    public static function pipe($name)
    {
        $pipe_array = N::z_pipe_values('get', array($name));
        return $pipe_array[0];
    }
    /**
     * Retrieve the settings for all registered pipes in an array.  With no argument, all pipes are retrieved.
     * @param array $names
     * @return array
     */
    public static function pipes(array $names = array())
    {
        return N::z_pipe_values('get', $names);
    }
    /**
     * Register a port for use within pipe functions.
     * @param string $name
     * @param mixed $value
     * @param array $settings
     * @param array $error_messages
     * @return void
     */
    public static function port_register($name, $value, array $settings = array(), array $error_messages = array())
    {
        self::z_port_values($mode = 'set', array('name' => $name, 'value' => $value, 'settings' => $settings, 'error_messages' => $error_messages));
    }
    /**
     * Retrieve registered port settings by name.
     * @param string $name
     * @return array
     */
    public static function port($name)
    {
        $port_array = self::z_port_values('get', array($name));
        $port = $port_array[0];

        if (!$port['valid']) throw new Exception('You cannot retrieve the value of an invalid port with port().  Check to make sure you are properly checking port validity in your pipe.');
    
        return $port['value'];
    }
    /**
     * Retrieve an array of ports by names.
     * @param array $names
     * @return array
     */
    public static function ports(array $names = array())
    {
        return self::z_port_values('get', $names);
    }
    public static function port_signature(array $ports)
    {
        foreach (self::ports($ports) as $port)
        {
            if (!$port['signature']) return false;
        }

        return true;
    }
    public static function port_validate(array $ports)
    {
        $rows = array();

        foreach (self::ports($ports) as $name => $port)
        {
            if (!is_null($port['error_message']))
                $rows[$port['name']] = array('error_message' => $port['error_message']);
        }

        return $rows;
    }
    /**
     * Encrypts value using Rijndael 256
     * @param string|int $value
     * @param string|int $key
     * @param string|int $salt
     * @return string
     */
    public static function security_encrypt($value, $key = null, $salt = null)
    {
        if (is_null($key))
            $key = self::config('encryption_key');
            
        if (is_null($salt))
            $salt = self::config('encryption_salt');
        
        $join = create_function('$value1,$value2', 'return base64_encode($value1).\'|\'.base64_encode($value2);');

        $hash = create_function('$value,$salt', 'return hash_hmac(\'sha256\', $value, $key, true);');
    
        $hashedKey = $hash($key, $salt);
    
        // time serves as nonce
        $mt = microtime(true);
    
        // concat base64'd mt and value
        $dataAndMessage = $join($mt, $value);
    
        // get mac for data and message
        $mac = $hash($dataAndMessage, $hashedKey);
    
        $dataAndMessageAndMac = $join($dataAndMessage, $mac);
    
        $cipherText = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $hashedKey, $dataAndMessageAndMac, 'cbc', $hash($mt, $salt));
    
        return $join($mt, $cipherText);
    }
    /**
     * Decrypts strings encrypted with the encrypt() function.
     * @param string $value
     * @param string|int $key
     * @param string|int $salt
     * @return string|int
     */
    public static function security_decrypt($value, $key = null, $salt = null)
    {
        if (is_null($key))
            $key = self::config('encryption_key');
            
        if (is_null($salt))
            $salt = self::config('encryption_salt');
        
        $break = create_function('$string', '$elements = explode(\'|\', $string);$elements[0] = base64_decode($elements[0]);$elements[1] = base64_decode($elements[1]);return $elements;');
    
        $hash = create_function('$value,$salt', 'return hash_hmac(\'sha256\', $value, $key, true);');
    
        // sha256_d converts key to 128 bit result in binary
        $hashedKey = $hash($key, $salt);
    
        list($mt, $cipherText) = $break($value);
    
        $dataAndMessageAndMac = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $hashedKey, $cipherText, 'cbc', $hash($mt, $salt));
    
        list($dataAndMessage, $mac) = $break($dataAndMessageAndMac);
    
        $hmacResult = $hash($dataAndMessage, $hashedKey);
    
        if($hmacResult != trim($mac))
        {
            throw new Exception('The encrypted value appears to be corrupted.');
        }
        else
        {
            list($mt, $message) = $break($dataAndMessage);
            return $message;
        }
    }
    /**
     * Start session by checking to see if one is already started before calling.
     * @return void
     */
    public static function session_start()
    {
        if (isset($_SESSION)) return;

        session_start();
    }
    /**
     * Creates and returns a connection handle for data sources (i.e., queries that read rows of a table, such as select queries.)
     * @param $dsn
     * @param $username
     * @param $password
     * @return unknown_type
     */
    public static function sql_source_connect($dsn = null, $username = null, $password = null)
    {
        if (is_null($dsn))
            $dsn = self::config('sql_source_dsn');
    
        if (is_null($username))
            $username = self::config('sql_source_username');
    
        if (is_null($password))
            $password = self::config('sql_source_password');
    
        $dbh = new PDO($dsn, $username, $password);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
        return $dbh;
    }
    /**
     * Creates and returns a connection handle for data actions (i.e., queries that alter rows of the table, such as insert, update, and delete.)
     * @param $dsn
     * @param $username
     * @param $password
     * @return unknown_type
     */
    public static function sql_action_connect($dsn = null, $username = null, $password = null)
    {
        if (is_null($dsn))
            $dsn = self::config('sql_action_dsn');
    
        if (is_null($username))
            $username = self::config('sql_action_username');
    
        if (is_null($password))
            $password = self::config('sql_action_password');
    
        $dbh = new PDO($dsn, $username, $password);
        $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
        return $dbh;
    }
    /**
     * Performs read-only ('SELECT') queries on tables and returns results.
     * @param array $query
     * @param $inputs
     * @param $dbh
     * @return array
     */
    public static function sql_source_query($query, array $inputs = array(), $dbh = null)
    {
        if (is_null($dbh))
            $dbh = self::sql_source_connect();
    
        $sth = $dbh->prepare($query);
    
        if (!$sth->execute($inputs))
        {
            throw new Exception('The source query could not be completed.');
        }
    
        return $sth->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * Performs queries that alter table rows, such as 'INSERT', 'UPDATE', and 'DELETE' queries.
     * @param array $query
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_query($query, array $inputs, $dbh = null)
    {
        if (is_null($dbh))
            $dbh = self::sql_action_connect();
    
        $sth = $dbh->prepare($query);
    
        if (!$sth->execute($inputs))
        {
            throw new Exception('The action query could not be completed.');
        }
    
        if(strtolower(substr(trim($query), 0, 6)) == 'insert')
            return $dbh->lastInsertId();
    
        return;
    }
    /**
     * Convenience function for deleting a row from a table by it's id (must follow the convention that the table primary key is named 'id'.)
     * @param $table_name
     * @param $id
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_delete($table_name, $id, $dbh = null)
    {
        return self::sql_action_query("DELETE FROM $table_name WHERE id = ?", array($id), $dbh);
    }
    /**
     * Convenience function for inserting an associative array row into a table (array keys must match the column names of the table.)
     * @param array $table_name
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_insert($table_name, array $inputs, $dbh = null)
    {
        $keys = array_keys($inputs);
        $fields = implode(',', $keys);
        $place_holders = implode(',', array_map(create_function('$v', 'return \':\'.$v;'), $keys));
        return self::sql_action_query("INSERT INTO $table_name ($fields) VALUES ($place_holders)", $inputs, $dbh);
    }
    /**
     * Convenience function for updating a table row with values from an associative array (array keys must match the column names of the table.)
     * @param array $table_name
     * @param $inputs
     * @param $dbh
     * @return unknown_type
     */
    public static function sql_action_update($table_name, array $inputs, $dbh = null)
    {
        $keys = array_keys($inputs);
        if (!isset($keys['id']))
            throw new Exception('The action_update method requires that the table your updating follow the convention that it has a primery key called "id".  No key was found that matched, so try using the action_query() function to perform your update.');
    
        unset($keys['id']);
        $update_values = implode(',', array_map(create_function('$v', 'return $v.\' = :\'.$v;'), $keys));
        return self::sql_action_query("UPDATE $table_name SET $update_values WHERE id = :id", $inputs, $dbh);
    }

    /**
     * Wrapper for PHP's str_replace that encodes the replaced values and provides the opportunity to allow string whitelists that shouldn't be encoded.
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @param array $whitelist
     * @return string
     */
    public static function str_replace($search, $replace, $subject, $whitelist = array(), $charset = 'UTF-8')
    {
        return str_replace($search, self::str_encode($replace, $whitelist), $subject);
    }
    /**
     * HTML-encodes output and allows whitelists of values that should be let through without encoding.
     * @param string $string
     * @param array $allowedHTML
     * @return string
     */
    public static function str_encode($string, $allowed_html = array(), $charset = 'UTF-8')
    {
        // use htmlspecialchars because it only does the 5 most important chars, and doesn't mess them up
        $string = htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
    
        if (count($allowed_html))
        {
            foreach($allowed_html as $tag)
            {
                // Save entitied version of tag, then look for occurances of it in string and replace them
                $entitied_tag = htmlspecialchars($tag, ENT_QUOTES, 'UTF-8');
                $string = str_replace($entitied_tag, $tag, $string);
            }
        }
    
        return $string;
    }
    /**
     * Immediately redirects from the current page to the URL.
     * @param string $url
     * @return void
     */
    public static function url_redirect($url)
    {
        ob_clean();
        header('Location: '.$url);
        exit();
    }
    public static function url_get_current()
    {
        if (isset($_SERVER['SCRIPT_URI']))
        {
            $url = $_SERVER['SCRIPT_URI'];

            if (count($_GET))
            {
                $url .= '?';
                foreach ($_GET as $key=>$value)
                {
                    $url .= $key.'='.$value.'&';
                }
            }

            return $url;
        }

        $url = '';
        $port = '';

        if (isset($_SERVER['HTTP_HOST']))
        {
            $colon = strpos($_SERVER['HTTP_HOST'], ':');

            if ($colon === false)
            {
                if (isset($_SERVER['SERVER_PORT']))
                {
                    $port = ':' . $_SERVER['SERVER_PORT'];
                }

                $url = $_SERVER['HTTP_HOST'];

            }
            else
            {
                $url = substr($_SERVER['HTTP_HOST'], 0, $colon);
                $port = substr($_SERVER['HTTP_HOST'], $colon);
            }
        }
        elseif(isset($_SERVER['SERVER_NAME']))
        {
            $url = $_SERVER['SERVER_NAME'];
            if (isset($_SERVER['SERVER_PORT']))
            {
                $port = ':' . $_SERVER['SERVER_PORT'];
            }
        }

        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
        {
            $url = 'https://' . $url;
            if ($port == ':443')
            {
                $port = '';
            }
        }
        else
        {
            $url = 'http://' . $url;
            if ($port == ':80')
            {
                $port = '';
            }
        }

        $url .= $port;

        if (isset($_SERVER['REQUEST_URI']))
        {
            $url .= $_SERVER['REQUEST_URI'];
        }
        else
        {
            throw Exception('We could not retrieve the URL for this page, so you\'ll have to turn off post reloading in nconfig.');
        }

        if (count($_GET))
        {
            $url .= '?';
            foreach ($_GET as $key=>$value)
            {
                $url .= $key.'='.$value.'&';
            }
        }

        return $url;
    }
    /**
     * Returns the immutable Nephtali value.
     * @param $name
     * @return unknown_type
     */
    public static function value($name)
    {
        $values_array = self::z_values($mode = 'get', $arr = array($name));
        return $values_array[0];
    }
    /**
     * Saves a value to be databound to page (values are immutable, so get them right the first time :)
     * @param $name
     * @param $value
     * @return unknown_type
     */
    public static function value_register($name, $value)
    {
        self::z_values($mode = 'set', $arr = array('name' => $name, 'value' => $value));
    }
    /**
     * Returns view markup after any required databinding.
     * @param string $name
     * @param string $markup
     * @param array $rows
     * @param array $databinding_functions
     * @param array $whitelists
     * @return string
     */
    public static function view_render($name, $markup, $rows = array(), $databinding_functions = array(), $whitelists = array())
    {
        $markup_sections = self::z_get_markup_sections($markup, $type = 'view', $name);

        if (count($markup_sections) != 3)
        {
            throw new Exception("We could not find the markup for the view '$name'.  Please make sure you have view comments in place for each of the views your pipes expect.");
        }

        return self::z_databind_view($markup_sections[1], $rows, $databinding_functions, $whitelists);
    }
    /**
     * Function primes config() function so subsequent calls retrieve the value associated with the argument key.
     * @param array $mode
     * @param array $dev
     * @param array $test
     * @param $prod
     * @return void
     */
    public static function z_config_save($mode, array $dev, array $test, array $prod)
    {
    	$defaults = array(
    	    'mode'						=> 'dev',
    	    'error_handling'            => NC::CONF_ERRORS_SUPPRESS,
    		'error_page'                => 'error.html',
    		'encryption_key'            => null,
    		'encryption_salt'           => null,
    		'upload_directory'          => '',
    		'upload_ftp_url'            => null,
    		'sql_action_dsn'            => null,
    		'sql_action_username'       => null,
    		'sql_action_password'       => null,
    		'sql_datasource_dsn'        => null,
    		'sql_datasource_username'   => null,
    		'sql_datasource_password'   => null,
            'post_request_handling'     => NC::CONF_POST_AUTO_REDIRECT_WO_FILES
        );
        
        if ($mode == 'dev')
        {
            $s = $dev;
            $s['mode'] = 'dev';
        }
        elseif ($mode == 'test')
        {
            $s = $test;
            $s['mode'] = 'test';
        }
        elseif ($mode == 'prod')
        {
            $s = $prod;
            $s['mode'] = 'prod';
        }
        else
        {
            throw new Exception('The mode must be one of the following strings: dev, test, prod.');
        }
    	
    	self::config(array_merge($defaults, $s));
    }
    /**
     * Returns the array of sections formed by a pipe, view, or data region.
     * @param string $markup
     * @param string $name
     * @param string $type
     * @return array
     */
    public static function z_get_markup_sections($markup, $type, $name = null)
    {
        if ($type != 'data' && count($sections = explode('<!--'.$type.':'.$name.'-->', $markup)) == 3)
        {
            return $sections;
        }
        else
        {
            if ($type == 'data')
            {
                if (count($sections = explode('<!--data-->', $markup)) == 3)
                {
                    return $sections;
                }
                else
                {
                    return array($markup);
                }
            }
            else
            {
                throw new Exception('The following '.$type.' could not be properly found in the markup: '.$name);
            }
        }
    }
    /**
     * Databinds associative array row to the markup according to the mappings.
     * @param string $markup
     * @param array $row
     * @param array $whitelists
     * @return string
     */
    public static function z_databind_row($markup, $row, $whitelists)
    {
        foreach ($row as $key => $value)
        {
            $markup = str_replace('{'.$key.'}', self::str_encode($value, $whitelists[$key]), $markup);
        }
        
        return $markup;
    }
    /**
     * Databinds rows of associative arrays to the markup contained within the <!--data--> region of the pipe markup.
     * @param $markup
     * @param $rows
     * @param $databinding_functions
     * @return unknown_type
     */
    public static function z_databind_view($markup, $rows, $databinding_functions, $whitelists)
    {    
        if (count($regions = self::z_get_markup_sections($markup, $type = 'data')) == 1)
        {
            return $markup;
        }
        
        $data_markup = $regions[1];
        
        $regions[1] = '';
        
        foreach ($rows as $row)
        {
            $markup___row = array('markup'=>$data_markup, 'row'=>$row);
            
            foreach ($databinding_functions as $func)
            {
                $markup___row = $func($markup___row); 
            }
            
            $regions[1] .= self::z_databind_row($markup___row['markup'], $markup___row['row'], $whitelists);
        }
        
        return implode('', $regions);
    }
    public static function z_handle_post_request()
    {
        if ((count($_POST) > 0) && (count($_FILES) < 1) && (N::config('post_request_handling') == self::CONF_POST_AUTO_REDIRECT_WO_FILES))
        {
            self::z_save_post_vars_to_session();
            self::z_set_redirect_flag();
            self::z_redirect_to_current_url();
        }
        else if((count($_POST) > 0) && (count($_FILES) > 0) && (N::config('post_request_handling') == N::AUTO_REDIRECT_ALL_POSTS))
        {
            /*
             * still have to add, don't do redirect for now
             *
             */
        }
        else if($_COOKIE['nephtali_post_redirect'] == 1)
        {
            self::session_start();

            if (isset($_SESSION['nephtali_post_vars']))
            {
                self::z_create_post_vars_from_session();
            }

            self::z_unset_redirect_flag();
        }
    }
    public static function z_save_post_vars_to_session()
    {
        self::session_start();
        
        foreach ($_POST as $key=>$value)
        {
            $_SESSION['nephtali_post_vars'][$key] = $value;
            unset($_POST[$key]);
        }
    }
    public static function z_create_post_vars_from_session()
    {
        self::session_start();

        foreach ($_SESSION['nephtali_post_vars'] as $key=>$value)
        {
            $_POST[$key] = $value;
            unset($_SESSION['nephtali_post_vars'][$key]);
        }
    }
    public static function z_set_redirect_flag()
    {
        setCookie('nephtali_post_redirect', 1);
    }
    public static function z_unset_redirect_flag()
    {
        setCookie('nephtali_post_redirect', 0);
    }
    public static function z_redirect_to_current_url()
    {
        self::url_redirect(self::url_get_current());
    }
    /**
     * File exists function that takes into account include path.
     *
     * @param string $file
     * @return bool
     */
    public static function z_file_exists($file)
    {
        $paths = explode(PATH_SEPARATOR, get_include_path());
    
        foreach ($paths as $path)
        {
            $path = $path.'/'.$file;
    
            if (file_exists($path))
            {
                return true;
            }
        }
    
        return false;
    }
    /**
     * Sets error handling mode for the page.
     *
     * @param int $mode
     */
    function z_set_error_handling($mode)
    {
        // E_STRICT will be part of PHP 6, so might as well start now
        error_reporting(E_ALL | E_STRICT);
    
        // Make things exceptions instead of errors (when possible)
        set_error_handler(array('N', 'z_error_handler'));
    
        if ($mode == 'dev' || $mode == 'test')
        {
            ini_set('display_errors', '1');
            set_exception_handler(array('N', 'z_dev_handler'));
        }
        else
        {
            ini_set('display_errors', '0');
            set_exception_handler(array('N', 'z_prod_handler'));
        }
    }
    /**
     * Handles errors by throwing them as exceptions with the underlying info contained in the message.
     *
     * @param int $errno
     * @param string $errstr
     * @param string $errfile
     * @param int $errline
     */
    public static function z_error_handler($errno, $errstr, $errfile, $errline)
    {
        // must take into account error suppressor (@) and not do anything with them (they equal 0)
        // http://framework.zend.com/issues/browse/ZF-3829
        if ($errno < 1)
        {
            $error_msg = "<dl><dt>Error Message:</dt><dd>$errstr</dd><dt>File:</dt><dd>$errfile</dd><dt>Line:</dt><dd>$errline</dd></dl>";
            throw new Exception($error_msg);
        }
    }
    /**
     * Handles exceptions not caught by the page when the application is in prod mode.
     * Any output is erased and the user is redirected to the default error page set in nconfig.php.
     * @param void $exception
     */
    public static function z_prod_handler($exception)
    {
        self::url_redirect(N::config('error_page'));
    }
    /**
     * Handles exceptions not caught by the page when the application is in dev mode.
     * Details of the exception and a backtrace of the code that led to the exception are output to the screen.
     * @param void $exception
     */
    public static function z_dev_handler($exception)
    {
        // Get rid of any output that's already buffered
        ob_end_clean();
    
        ?>
        <html>
        <head>
        <title>Debug output for unhandled exception</title>
        </head>
        <body>
        <h1>Debug output for unhandled exception</h1>
        <?php echo $exception->getMessage(); ?><br />
        <h2>Debug Backtrace</h2>
        <pre><?php echo self::str_encode(print_r(debug_backtrace(), true)); ?></pre>
        </body>
        </html>
        <?php
        die();
    }
    
    public static function z_pipe_values($mode, array $arr)
    {
        static $pipes = array();
        
        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr) == 1)
            {
                if (!isset($pipes[$arr[0]]))
                    throw new Exception('There is no registered pipe called "'.$arr[0].'" for this page.');
                    
                return array($pipes[$arr[0]]);
            }
            elseif (count($arr) > 1)
            {
                return array_intersect_key($pipes, array_fill_keys($arr, 0));   
            }
            else
            {
                return $pipes;   
            }
        }
        // set the pipe given in the array
        else
        {
            if (!isset($pipes[$arr['name']]))
            {
                $pipes[$arr['name']] = array('name' => $arr['name'], 'function' => $arr['function'], 'required_pipes' => $arr['required_pipes']);
                return;    
            }
            else
            {
                throw new Exception('There is already a pipe called "'.$arr['name'].'" that has been registered for this page.');
            }
        }
    }
    
    public static function z_port_values($mode, array $arr)
    {
        static $ports = array();

        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr) == 1)
            {
                if (!isset($ports[$arr[0]]))
                    throw new Exception('There is no registered port called "'.$arr[0].'" for this page.');
                    
                return array($ports[$arr[0]]);
            }
            elseif (count($arr) > 1)
            {
                return array_intersect_key($ports, array_fill_keys($arr, 0));
            }
            else
            {
                return $ports;   
            }
        }
        // set the port given in the array
        else
        {
            if (!isset($ports[$arr['name']]))
            {   
                // used for error output
                $formatted_name = str_replace('_', ' ', ucfirst($arr['name']));

                $defaults = array(
                    'min_length'         => 1,
                    'max_length'         => 10,
                    'min_value'		     => 1,
                    'max_value'			 => 9999999999,
                    'filter'             => NC::FILTER_INT,
                    'regex'				 => null,
                    'encrypted'          => false,
                    'allow_null'         => false,
                    'whitelist'          => array(),
                    'allow_array'        => false,
                    'formatted_name'     => $formatted_name
                );
                
                $settings = array_merge($defaults, $arr['settings']);
                
                $whitelist = "'".$settings['formatted_name']."' only accepts the following values:  ".implode(',', $settings['whitelist']).".";

                switch ($settings['filter'])
                {
                    case NC::FILTER_INT:
                        $error_message = "'{$settings['formatted_name']}' only accepts integers.";
                        break;
                    case NC::FILTER_DOUBLE:
                        $error_message = "'{$settings['formatted_name']}' only accepts positive or negative numbers with or without decemals.";
                        break;
                    case NC::FILTER_ALPHANUM:
                        $error_message = "'{$settings['formatted_name']}' only accepts numbers and letters as input.";
                        break;
                    case NC::FILTER_VAR_NAME:
                        $error_message = "'{$settings['formatted_name']}' only accepts numbers, letters, underscores, and dashes as input.";
                        break;
                    case NC::FILTER_US_PHONE:
                        $error_message = "'{$settings['formatted_name']}' only accepts valid US phone numbers as input.";
                        break;
                    case NC::FILTER_US_ZIP:
                        $error_message = "'{$settings['formatted_name']}' only accepts valid US zip codes as input.";
                        break;
                    case NC::FILTER_URL:
                        $error_message = "'{$settings['formatted_name']}' only accepts valid URLs as input.";
                        break;
                    case NC::FILTER_EMAIL:
                        $error_message = "'{$settings['formatted_name']}' only accepts valid email addresses as input.";
                        break;
                    case NC::FILTER_TEXT:
                        $error_message = "'{$settings['formatted_name']}' only accepts a single line of basic text as input (no HTML input allowed.)";
                        break;
                    case NC::FILTER_TEXT_MULTILINE:
                        $error_message = "'{$settings['formatted_name']}' only accepts basic text as input (no HTML input allowed.)";
                        break;
                    case NC::FILTER_XML:
                        $error_message = "'{$settings['formatted_name']}' only accepts text and XML as input.";
                        break;
                    case NC::FILTER_CUSTOM:
                        $error_message = "'{$settings['formatted_name']}' is not valid data.";
                    default:
                        $error_message = "The data received was invalid.";
                }
                
                $default_messages = array(
                    'min_length'		 => "'{$settings['formatted_name']}' has a minimum length of  {$settings['min_length']}.",
                    'max_length'		 => "'{$settings['formatted_name']}' has a maximum length of  {$settings['max_length']}.",
                    'allow_null'		 => "'{$settings['formatted_name']}' is an expected value, and we did not recieve it.",
                    'whitelist'			 => $whitelist,
                    'allow_array'		 => "'{$settings['formatted_name']}' does not allow arrays of data.",
                    'filter'             => $error_message
                );

                $error_messages = array_merge($default_messages, $arr['error_messages']);
                
                $ports[$arr['name']] = self::z_port_validate(array('name' => $arr['name'], 'formatted_name' => $formatted_name, 'value' => $arr['value'], 'settings' => $settings, 'error_messages' => $error_messages));
                return;    
            }
            else
            {
                throw new Exception('A port called "'.$arr['name'].'" has already been registered for this page.');
            }
        }   
    }
    public static function z_values($mode, $arr = array())
    {
        static $values = array();

        // retrieve the pipes in the array
        if ($mode == 'get')
        {
            if (count($arr) == 1)
            {
                if (!isset($values[$arr[0]]))
                    throw new Exception('There is no registered value called "'.$arr[0].'" for this page.');

                return array($values[$arr[0]]);
            }
            elseif (count($arr) > 1)
            {
                return array_intersect_key($values, array_fill_keys($arr, 0));
            }
            else
            {
                return $values;
            }
        }
        else
        {
            if (isset($values[$arr['name']]))
            {
                throw new Exception('A value called "'.$arr['name'].'" has already been registered for this page.');
            }

            $values[$arr['name']] = $arr['value'];
        }
    }
    public static function z_port_validate(array $arr)
    {   
        // check for NULLs
        if (is_null($arr['value']))
        {
            if ($arr['settings']['allow_null'])
            {
                return self::z_port_declare_valid($arr);
            }
            else
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'allow_null');
            }
        }
        
        // check for arrays
        if (is_array($arr['value']))
        {
            // take care of this later.
        }
        else
        {
            // check if the value has been encrypted, and if so, decrypt
            if($arr['settings']['encrypted'])
            {
                $arr['value'] = self::security_decrypt($arr['value']);
            }
            // whitelist validation
            if(count($arr['settings']['whitelist']) > 0)
            {
                if (!in_array($arr['value'], $arr['settings']['whitelist']))
                {
                    return self::z_port_declare_invalid($arr, $failed_test = 'whitelist');
                }
            
                return self::z_port_declare_valid($arr);
            } 
            // if value is '' and minLength is 0, don't wast processing
            if ($value == '' && $arr['settings']['min_length'] == 0)
            {
                return self::z_port_declare_valid($arr);
            }
            // check max length
            if(strlen($arr['value']) > $arr['settings']['max_length'])
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'max_length');
            }
            // check min length
            if(strlen($arr['value']) < $arr['settings']['min_length'])
            {
                return self::z_port_declare_invalid($arr, $failed_test = 'min_length');
            }
            // filter
            switch ($arr['settings']['filter'])
            {
                case NC::FILTER_INT:
                    if (!preg_match(NC::REGEX_INT, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_DOUBLE:
                    if (!preg_match(NC::REGEX_DOUBLE, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_ALPHANUM:
                    if (!preg_match(NC::REGEX_ALPHANUM, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_VAR_NAME:
                    if (!preg_match(NC::REGEX_VAR_NAME, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_US_PHONE:
                    if (!preg_match(NC::REGEX_US_PHONE, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_US_ZIP:
                    if (!preg_match(NC::REGEX_US_ZIP, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_URL:
                    if (!preg_match(NC::REGEX_URL, $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_EMAIL:
                    if (!preg_match(NC::REGEX_EMAIL, $arr['value']))
                    {
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');
                    }
                    break;
                case NC::FILTER_TEXT:
                    if (!preg_match(NC::REGEX_TEXT, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_TEXT_MULTILINE:
                    if (!preg_match(NC::REGEX_TEXT_MULTILINE, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_XML:
                    if (!preg_match(NC::REGEX_XML, $arr['value']) || self::z_port_contains_xss($arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;
                case NC::FILTER_ALL:
                    break;
                case NC::FILTER_CUSTOM:
                    if (!preg_match($arr['settings']['regex'], $arr['value']))
                        return self::z_port_declare_invalid($arr, $failed_test = 'filter');

                    break;     
                default:
                    throw new Exception('The filter supplied for the port "'.$arr['name'].'" is not valid.');
           }

            return self::z_port_declare_valid($arr);
        }			   
    }
    public static function z_port_declare_valid(array $arr)
    {
        $arr['valid'] = true;
        $arr['signature'] = true;
        $arr['error_message'] = null;
        return $arr;
    }
    public static function z_port_declare_invalid(array $arr, $failed_test)
    {
        $arr['error_message'] = $arr['error_messages'][$failed_test];
        $arr['valid'] = false;
        ($failed_test == 'allow_null')?($arr['signature'] = false):($arr['signature'] = true);
        return $arr;
    }
    public static function z_port_contains_xss($value)
    {
		$xss_hackemes = array(
			'%3c', // <
			'&lt', // <
			'pa==',// <
			'&gt', // >
			'%3E', // >
			'pg==',// >
			'&quo',// "
			'%22', // "
			'ig==',// "
			'#27', // '
			'jw==',// '
			'&#',  // generally not needed
			'\x',  // generally not needed
			'\u',  // generally not needed
			'javascript',
			'onsubmit',
			'onabort',
			'onblur',
			'onchange',
			'onfocus',
			'onmouse',
			'onload',
			'onreset',
			'onselect',
			'onunload',
			'onerror',
			'onclick',
			'<script'
						);

		$lc_value = strtolower($value);

		foreach($xss_hackemes as $hackeme)
		{
			if(strpos($lc_value, $hackeme)===false) {
				continue;
			} else {
				return true;
			}
		}

		return false;
    }
    public static function z_pipe_process_registered($markup)
    {
        /**
         * Get configuration set up.
         */
        require('nconfig.php');
        /**
         * Configure environment with config settings.
         */
        N::z_set_error_handling(N::config('mode'));
        /**
         * Register pipes in npipe.php (authentication, etc.)
         */
        require('npipes.php');
        /**
         * Register code-behind pipes and ports, if present
         */
        if (self::z_file_exists('nsite'.$_SERVER['PHP_SELF']))
        {
            require('nsite'.$_SERVER['PHP_SELF']);
        }

        $nmode_whitelist = array('htmlfrag','validate');

        self::port_register($name = 'nmode', $value = $_GET['nmode'], $settings = array('whitelist' => $nmode_whitelist));
        self::port_register($name = 'npipe', $value = $_GET['npipe'], $settings = array('max_length' => 40, 'filter' => NC::FILTER_VAR_NAME));
        self::port_register($name = 'nports', $value = $_GET['nports'], $settings = array('max_length' => 700, 'filter' => NC::FILTER_CUSTOM, 'regex' => '/^[-,a-zA-Z0-9_]+$/'));

        if (self::port_signature($ports = array('nmode', 'nports')))
        {
            // return a JSON array containing validation info
            if($errors = self::port_validate($ports = array('nmode', 'nports')))
            {
                throw new Exception('The values nmode and nports were not valid for this request.');
            }

            $ports = explode(',', self::port('nports'));

            if (!$rows = self::port_validate($ports))
            {
                return '[]';
            }
            else
            {
                $invalid_values = '[';

                foreach($rows as $name => $row)
                {
                    $invalid_values .= '["'.$name.'","'.str_replace('"', '\"', $row['error_message']).'"]';
                }

                return $invalid_values.']';
            }
        }
        elseif (self::port_signature($ports = array('nmode', 'npipe')))
        {
            if (count(self::port_validate($ports = array('nmode', 'npipe'))))
                throw new Exception('The nmode and npipe ports did not contain valid values.');

            $pipe_name = self::port('npipe');
            $pipes = self::pipes(array($pipe_name));
            $markup_sections = N::z_get_markup_sections($markup, $type = 'pipe', $name = $pipe_name);
            $rendering_function = $pipes[$pipe_name]['function'];
            $markup = call_user_func_array($rendering_function, array($markup_sections[1]));
        }
        else
        {
            foreach(self::pipes() as $name => $settings)
            {
                $markup_sections = N::z_get_markup_sections($markup, $type = 'pipe', $name);
                $rendering_function = $settings['function'];
                $markup_sections[1] = call_user_func_array($rendering_function, array($markup_sections[1]));
                $markup = implode('', $markup_sections);
            }
        }

        $markup = self::z_pipe_render_values($markup);

        return $markup;
    }
    public static function z_pipe_render_values($markup)
    {
        return self::z_databind_row($markup, $row = self::z_values($mode = 'get'), $whitelists = array());
    }
}

echo N::z_pipe_process_registered($markup = ob_get_clean());

?>